/**
* file name  : Jenoware/it/ciano/jenoware/GetLINKAGE.java
* authors    : Luciano Xumerle
* created    : gio 10 nov 2005 11:15:06 CET
*
* Copyright (c) 2004-2005 Luciano Xumerle. All rights reserved.
*
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package it.ciano.jenoware;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Arrays;
import java.io.FileReader;
import java.io.File;
import java.io.BufferedReader;
//import java.util.Entry;
import java.util.Map;
import it.ciano.util.CFile;
import it.ciano.util.CString;

/**
 * The class GetLINKAGE allow an user to create the datafile and pedfile needed
 * to use the LINKAGE package.
 * The class support only 3 (of 4) types of phenotypes: a, b and d.
 * a) affection status: 0 (unknown), 1 (not affected), 2 (affected);
 * b) Allele numbers of genetic markers;
 * d) quantitative traits.
 * For more informations:
 * ftp://linkage.rockefeller.edu/software/linkage/user.txt
 * and ftp://linkage.rockefeller.edu/software/linkage/linkhelp.txt
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
public class GetLINKAGE extends GetFile {

    /**
     * is an hash of MarkerFreq object.
     * the first key is a marker name pointing to a MarkerFreq object.
     */
    private HashMap _allelePos;

    private int number_of_phenotypes;
    private int number_of_markers;

    /**
     * is an hash.
     * keys are the phenotype name.
     * values are the phenotype type
     */
    private HashMap _phenoType;

    /**
     * is an hash.
     * keys are phenotype name
     * values are HashMap of the observed values into phenotype
     */
    private HashMap _phenoValues;

    private final static String sep = " ";
    private final static String unk = "0";

    /**
     * The value of the public constant YESNO shows the phenotypes
     * with possible values 0 (unknown), 1 (not affected), 2 (affected).
     * Empty fields are replaced with 0.
     */
    public final static String YESNO = "a";

    /**
     * The value of the public constant ALLELE shows the phenotypes
     * with values as number of alleles of a genetic marker.
     * Empty fields are replaced with 0.
     */
    public final static String ALLELE = "b";

    /**
     * The value of the public constant QUANTITATIVE shows quantitative
     * traits. Unknown phenotypes are entered as 0.0.
     */
    public final static String QUANTITATIVE = "d";

    private String gene_order[];

    /**
     * Class Constructor.
     *
     * @param pedigree a populated Pedigree object.
     */
    public GetLINKAGE ( Pedigree pedigree ) {
        this ( pedigree, null );
    }

    /**
     * Class Constructor.
     *
     * @param pedigree a populated Pedigree object.
     * @param genoOrder ordered list of markers.
     */
    public GetLINKAGE ( Pedigree pedigree, String[] genoOrder ) {
        super ( pedigree );
        if ( genoOrder == null )
            gene_order = getPed().getMarkersList();
        else
            gene_order = genoOrder;
        number_of_phenotypes = setPhenotypeType();
        number_of_markers = setAllelePos();
    }

    /**
     * Returns the number of phenotypes saved in the destination files.
     *
     * @return the number of Phenotypes saved in the destination files.
     */
    public int getNumberOfPhenotypes() {
        return number_of_phenotypes;
    }

    /**
     * Returns the number of genetic markers saved in the destination files.
     *
     * @return the number of genetic markers saved in the destination files.
     */
    public int getNumberOfMarkers() {
        return number_of_markers;
    }

    /**
     * Overriding toString method
     *
     * @return all informations that will be contained into the destination files.
     */
    public String toDAT() {
        String[] phs = getPed().getPhenotypesList();
        int tot = number_of_phenotypes + number_of_markers;

        StringBuffer res = new StringBuffer();
        // the total number of phenotype (include markers)
        res.append ( tot );
        res.append ( sep );
        // risklocus is set to 0 by default
        res.append ( "0" );
        res.append ( sep );
        // sexlinked is set to 0 (autosomal) by default
        res.append ( "0" );
        res.append ( sep );
        // program used is set to 5 (mlink) by default
        res.append ( "5\n" );
        // program uses mutations rate=0
        // and loci are supposed to be in linkage equilibrium
        res.append ( "0 0 0 0\n" );
        // print phenotype order
        for ( int i = 1; i <= tot; i++ ) {
            res.append ( i );
            if ( i < tot )
                res.append ( sep );
        }
        res.append ( " << order\n" );

        /**
         * print type of every phenotype:
         * 0: quantitative var;
         * 1: affection status [0,1,2];
         * 2: binary factors (not supported);
         * 3: numbered alleles.
         */

        // add phenotypes
        if ( phs!=null )
            for ( int i = 0; i < phs.length; i++ ) {
                String type = ( String ) _phenoType.get ( phs[ i ] );
                if ( type.equals ( YESNO ) ) {
                    res.append ( "1  2  << pheno: " );
                    res.append ( phs[ i ] );
                    res.append ( " - AFFECTATION STATUS, NO. OF ALLELES\n" );
                    res.append ( "0.50000 0.500000  << GENE FREQUENCIES\n" );
                    res.append ( "1  << NO. OF LIABILITY CLASSES\n" );
                    res.append ( "0.200000 0.500000 0.900000 << PENETRANCES\n" );
                }
                // else if ( type.equals( QUANTITATIVE ) )  {     }
            }

        // add markers
        for ( int i = 0; i < number_of_markers; i++ ) {
            MarkerFreq tt = ( MarkerFreq ) _allelePos.get ( gene_order[ i ] );
            Allele[] aa = tt.getOrderedAlleleList();
            // code 3 for numbered alleles and total number of observed alleles
            res.append ( "3 " );
            res.append ( aa.length );
            res.append ( " << marker: " );
            res.append ( gene_order[ i ] );
            res.append ( " - numbered allele code, tot number of alleles\n" );
            // alleles frequencies ordered by positions
            res.append ( getOrderedAlleleFrequencies ( gene_order[ i ] ) );
            res.append ( " << gene frequencies\n" );
        }

        // sex-differences and interference
        res.append ( "0" );
        res.append ( sep );
        res.append ( "0 << sex-differences and interference\n" );
        // recombination rates (should be number of phenotypes - 1 values)
        for ( int i = 0; i < ( tot - 1 ); i++ ) {
            res.append ( "0.1" );
            res.append ( sep );
        }
        res.append ( "<< recombination rates, sex-differences and interference\n" );

        return res.toString();
    }

    /**
     * Overriding toString method
     *
     * @return all informations that will be contained into the destination files.
     */
    public String toPED() {
        StringBuffer res = new StringBuffer();
        Sbj[] ids = getPed().getListSbj();
        for ( int id = 0; id < ids.length; id++ ) {
            String[] fams=ids[ id ].getFML();
            for ( int ff=0; ff<fams.length; ff++ ) {
                String ss = sbjLine ( fams[ff], ids[ id ], GetLINKAGE.sep, GetLINKAGE.unk );
                if ( ss != null )
                    res.append ( ss );
            }
        }
        return res.toString() +"end\n";
    }

    /**
     * Overriding toString method
     *
     * @return all informations that will be contained into the destination files.
     */
    public String toString() {
        StringBuffer res = new StringBuffer();
        res.append ( toDAT() );
        res.append ( "\n\n###########################\n\n" );
        res.append ( toPED() );
        return res.toString();
    }

    /**
     * Returns the String containing a valid pedfile line for the given Sbj ID.
     *
     * @param sbj a Sbj object.
     * @param sep the separator used into the destination file.
     * @param unk value used to mark unknown field.
     * @return String containing a valid pedfile line for the given Sbj ID.
     */
    final public String sbjLine ( String fml, Sbj sbj, String sep, String unk ) {
        StringBuffer res = new StringBuffer();
        if ( "0".equals ( fml ) )
            return null;
        // add pedigree info
        res.append ( fml );
        res.append ( sep );
        res.append ( sbj.getSBJ ( fml ) );
        res.append ( sep );
        res.append ( sbj.getFTH ( fml ) );
        res.append ( sep );
        res.append ( sbj.getMTH ( fml ) );
        res.append ( sep );
        res.append ( sbj.getSEX() );
        res.append ( sep );
        // add phenotype info
        String[] pheno = getPed().getPhenotypesList();
        if ( pheno==null ) {
            res.append ( "0" );
            res.append ( sep );
        } else {
            for ( int i = 0; i < pheno.length; i++ ) {
                if ( ( ( String ) _phenoType.get ( pheno[ i ] ) ).equals ( YESNO ) ) {
                    if ( sbj.getPhenotype ( pheno[ i ] ).getValue().equals ( "" ) )
                        res.append ( unk );
                    else
                        res.append ( sbj.getPhenotype ( pheno[ i ] ).getValue() );
                    res.append ( sep );
                }

                else if ( ( ( String ) _phenoType.get ( pheno[ i ] ) ).equals ( QUANTITATIVE ) ) {
                    // PRINT NOTHING NOT IMPLEMENTED
                    continue;
                    /*
                    if ( ss.getPhenotype( pheno[ i ] ).getValue().equals( "" ) )
                        res.append( "0.0" );
                    else
                        res.append( ss.getPhenotype( pheno[ i ] ) );
                    res.append( sep );
                    */
                }

                else {
                    res.append ( "0" );
                    res.append ( sep );
                }
            }
        }

        // add genotype info
        for ( int i = 0; i < gene_order.length; i++ ) {
            Genotype gg = sbj.getGenotype ( gene_order[ i ] );
            if ( gg.isValid() ) {
                boolean num=gg.isNumeric();
                String sepa='/'+sep;
                if ( num ) {
                    res.append ( gg.getAllele ( 1 ) );
                    res.append ( sepa );
                    res.append ( gg.getAllele ( 2 ) );

                } else {
                    MarkerFreq hh = ( MarkerFreq ) _allelePos.get ( gene_order[ i ] );
                    res.append ( hh.getAllelePosition ( gg.getAllele ( 1 ) ) );
                    res.append ( sepa );
                    res.append ( hh.getAllelePosition ( gg.getAllele ( 2 ) ) );
                }
                res.append ( sep );
            } else {
                res.append ( unk );
                res.append ( '/' );
                res.append ( sep );
                res.append ( unk );
                res.append ( sep );
            }
        }
        return res.toString().trim() +"\n";
    }

    /**
     * Creates all the expected files.
     *
     * @return true if all operations are ok.
     */
    public boolean createsFile() {
        return ( CFile.saveString ( getFilename() + ".dat", toDAT() )
                 && CFile.saveString ( getFilename() + ".pre", toPED() ) );
    }

    // PRIVATE METHOD // PRIVATE METHOD // PRIVATE METHOD // PRIVATE METHOD //
    // PRIVATE METHOD // PRIVATE METHOD // PRIVATE METHOD // PRIVATE METHOD //
    // PRIVATE METHOD // PRIVATE METHOD // PRIVATE METHOD // PRIVATE METHOD //

    /**
     * Populates the private _phenoType HashMap.
     */
    final private int setPhenotypeType() {
        _phenoType = new HashMap();
        int total = 0;
        String[] pheno = getPed().getPhenotypesList();
        if ( pheno==null )
            return total;
        Sbj[] ids = getPed().getListSbj();
        _phenoValues = new HashMap();
        for ( int id = 0; id < ids.length; id++ ) {
            for ( int i = 0; i < pheno.length; i++ ) {
                if ( !_phenoValues.containsKey ( pheno[ i ] ) )
                    _phenoValues.put ( pheno[ i ], new HashMap() );
                String val = ids[ id ].getPhenotype ( pheno[ i ] ).getValue().trim();
                ( ( HashMap ) _phenoValues.get ( pheno[ i ] ) ).put ( val, null );
            }
        }

        /**
         * now we have a HashMap with phenotype name as keys and an HashMap as values.
         * each values HashMap contains all the values available for a phenotype.
         * Now the program scans the values of each phenotype and try to set the
         * correct phenotype type.
         */
        for ( Iterator it = _phenoValues.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = ( Map.Entry ) it.next();
            String kk = ( String ) entry.getKey();
            HashMap vv = ( HashMap ) entry.getValue();
            String aa[] = new String[ vv.size() ];
            int ind = 0;
            for ( Iterator it2 = vv.entrySet().iterator(); it2.hasNext(); ) {
                Map.Entry eee = ( Map.Entry ) it2.next();
                aa[ ind ] = ( ( String ) eee.getKey() );
                ind++;
            }
            Arrays.sort ( aa );
            String res = CString.join ( aa, "" );
            // if join is 012 the we have a null value, affected or not affected
            // else we have a phenotype with numbered data data.
            if ( res.equals ( "0" ) || res.equals ( "1" )
                    || res.equals ( "2" ) || res.equals ( "x" )
                    || res.equals ( "12" ) || res.equals ( "01" ) || res.equals ( "02" )
                    || res.equals ( "012" ) || res.equals ( "12x" ) ) {
                _phenoType.put ( kk, YESNO );
                total++;
            } else {
                _phenoType.put ( kk, QUANTITATIVE );
                // total++;        NOT IMPLEMENTED
            }
        }
        return total;
    }

    /**
     * Populates the private _allelePos HashMap.
     */
    final private int setAllelePos() {
        _allelePos = new HashMap();
        for ( int i = 0; i < gene_order.length; i++ ) {
            _phenoType.put ( gene_order[ i ], ALLELE );
            if ( !_allelePos.containsKey ( gene_order[ i ] ) )
                _allelePos.put ( gene_order[ i ], new MarkerFreq ( getPed(), gene_order[ i ] ) );
        }
        return gene_order.length;
    }

    /**
     * Returns a String containing the ordered frequencies of each allele of a marker.
     *
     * @param marker the name of a marker.
     * @return a String containing the ordered frequencies of each allele of a marker.
     */
    final private String getOrderedAlleleFrequencies ( String marker ) {
        MarkerFreq tt = ( MarkerFreq ) _allelePos.get ( marker );
        Allele[] pp = tt.getOrderedAlleleList();
        String[] aa = new String[ pp.length ];
        for ( int i = 0; i < pp.length; i++ )
            aa[ i ] = String.valueOf (
                          tt.getRelatedAlleleFreq ( pp[ i ].toString() ) );
        return CString.join ( aa, " " );
    }

    /**
     * Returns an object containing header data stored in a LINKAGE dat file.
     *
     * @param datFile the path to the dat file.
     * @return an object containing header data stored in a LINKAGE dat file.
     */
    public static final CsvHead getHeaderFromDatFile ( String datFile  ) {
        CsvHead header=null;
        try {
            // read the traits and marker positions
            ArrayList head=new ArrayList();
            head.add ( "id" );
            head.add ( "prb" );
            head.add ( "fml" );
            head.add ( "sbj" );
            head.add ( "fth" );
            head.add ( "mth" );
            head.add ( "sex" );
            BufferedReader buf = new BufferedReader ( new FileReader ( new File ( datFile ) ) );
            String text;
            while ( ( text = buf.readLine() ) != null ) {
                text = text.trim();
                if ( text.equals ( "" ) )
                    continue;
                String[] type= text.split ( "\\s+" );
                if ( type[0].equals ( "M" ) ) {
                    head.add ( type[1]+"_1" );
                    head.add ( type[1]+"_2" );
                } else if ( type[0].equals ( "E" ) )
                    continue;
                else
                    head.add ( type[1] );
                header=new CsvHead ( ( String[] ) head.toArray ( new String[head.size() ] ) );
            }
            return header;
        } catch ( Exception ex ) {
            System.err.println ( "Error: DAT " + datFile + " has not been added." );
        }
        return header;
    }

    /**
     * Adds the values contained in the given PED file.
     * The program retrieve missing information from the dat file.
     *
     * @param pedFile the input file (you need the dat file too).
     * @return true or false.
     */
    public static boolean addPEDfile ( Pedigree ped, String pedFile, boolean mantainAllelesOrder ) {
        String datFile=pedFile.substring ( 0,   pedFile.indexOf ( ".ped" )  ) + ".dat";
        String text;
        boolean isok = false;
        CsvHead head=GetLINKAGE.getHeaderFromDatFile (  datFile  );
        if ( head==null )
            return false;
        try {
            // read the traits and marker positions
            BufferedReader buf = new BufferedReader ( new FileReader ( new File ( pedFile ) ) );
            while ( ( text = buf.readLine() ) != null ) {
                text = text.trim();
                if ( text.equals ( "" ) || text.equals ( "end" ) )
                    continue;
                isok = true;
                String[] aa=text.split ( "[\\/\\s]+" );
                String[] bb=new String[ aa.length +2  ];
                bb[0]=aa[0]+"+"+aa[1];
                bb[1]="0";
                for ( int i=2; i<bb.length; i++ )
                    bb[i]=aa[i-2];
                head.addRow ( ped, bb, mantainAllelesOrder );
            }

            return isok;
        } catch ( Exception ex ) {
            System.err.println ( "Error: PED " + pedFile + " has not been added." );
        }
        return isok;
    }



} // end class

